package com.example.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.mac.MacEngine;
import com.example.lotterysystem.common.config.ExecutorConfig;
import com.example.lotterysystem.common.exception.ServiceException;
import com.example.lotterysystem.common.utils.JacksonUtil;
import com.example.lotterysystem.common.utils.MailUtil;
import com.example.lotterysystem.controller.param.DrawPrizeParam;
import com.example.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.example.lotterysystem.dao.dataobject.WinningRecordDO;
import com.example.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.example.lotterysystem.dao.mapper.WinningRecordMapper;
import com.example.lotterysystem.service.DrawPrizeService;
import com.example.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.example.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.example.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.example.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.example.lotterysystem.service.enums.ActivityStatusEnum;
import com.example.lotterysystem.service.enums.ActivityUserStatusEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    private static final Logger logger = LoggerFactory.getLogger(MqReceiver.class);

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;



    @RabbitHandler
    public void process(Map<String, String> message){
        //成功接受到队列中的消息
        logger.info("MQ成功接收到消息，message:{}", JacksonUtil.writeValueAsString(message));
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);
        //处理抽奖流程

        try{
            //校验抽奖请求是否有效
            // 1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的两个请求
            // 2、param：最后一个奖项-》
            //      处理param1：活动完成、奖品完成
            //      处理param2: 回滚活动、奖品状态
            if(!drawPrizeService.checkDrawPrizeParam(param)){
                return;
            }
            //状态扭转处理
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinnerRecords(param);
            //通知中奖者（邮箱）
            //抽奖之后的后续流程，异步处理 并发
            syncExecute(winningRecordDOList);
        }catch (ServiceException e){
            logger.error("处理 MQ 消息异常！ {}:{}", e.getCode(), e.getMessage(), e);
            //需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常:消息重试
            throw e;
        }catch (Exception e){
            logger.error("处理 MQ 消息异常！", e);
            //需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常:消息重试
            throw e;
        }
    }


    /**
     * 处理抽奖异常的回滚：恢复处理请求之前的库表状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        //1.状态回滚：活动、奖品、人员
        //判断状态是否需要回滚
        //不需要:return
        if(!statusNeedRollback(param)){
            return;
        }
        //需要:回滚
        rollbackStatus(param);
        //2.回滚中奖者名单
        //是否需要回滚
        //不需要：return
        if(!winnerNeedRollback(param)){
            return;
        }
        //需要：回滚
        rollbackWinner(param);
    }
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断活动+奖品+人员相关状态是否已经扭转
        //扭转状态时由于保证了事务一致性，要么都扭转，要么都没扭转（不包括活动），所以人员、奖品判断一个即可
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())){
            return true;
        }
        return false;
    }


    /**
     * 恢复相关状态
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        //判断活动中是否有中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 回滚中奖记录:删除中奖记录的中奖者
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }



    /**
     * 并发处理抽奖后续流程
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //通过线程池处理

        //短信通知
//        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        //邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));
    }


    /**
     * 发送邮件
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if(CollectionUtils.isEmpty(winningRecordDOList)){
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }
        for(WinningRecordDO winningRecordDO : winningRecordDOList){
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context);
        }
    }


    /**
     * 发送短信
     * @param winningRecordDOList
     */
//    private void sendMessage(List<WinningRecordDO> winningRecordDOList) {
//    }


    /**
     * 状态扭转
     */
    public void statusConvert(DrawPrizeParam param){
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }
}
